
require 'cgi'

module CornoStyle
  class Default
    attr_accessor :text, :tokens

    def initialize(text, plugin = nil)
      @text = text.gsub(/(\r\n)|(\r)/, "\n")
      @tokens = []
      @plugin = plugin
    end

    def style
      'CornoStyle'
    end

    BLOCK_STYLES = [ 
      { :type => :pre,        :regexp => /^>\|\|\n(.+?)\|\|<\n?/m },
      { :type => :blockquote, :regexp => /^>>\n(.+?)<<\n?/m },
      { :type => :table,      :regexp => /^(\|\|.+?)(\n|$)/m },
      { :type => :blist,      :regexp => /^(-{1,4}) (.+?)(\n|$)/m },
      { :type => :nlist,      :regexp => /^(\+{1,4}) (.+?)(\n|$)/m },
      { :type => :title,      :regexp => /^(\*{2,4}) (.+?)(\n|$)/m },
#     { :type => :plugin,     :regexp => /\{\{(.+?)\}\}/m},
    ]

    BLOCK_STYLE_RULES = {
      :pre             => { :regexp => /^>\|\|\n(.+?)\|\|<\n?/m, },
      :blockquote      => { :regexp => /^>>\n(.+?)<<\n?/m, },
      :table           => { :regexp => /^(\|\|.+?)(\n|$)/m },
      :blist           => { :regexp => /^(-{1,4}) (.+?)(\n|$)/m },
      :nlist           => { :regexp => /^(\+{1,4}) (.+?)(\n|$)/m },
      :title           => { :regexp => /^(\*{2,4}) (.+?)(\n|$)/m, },
#     :plugin          => { :regexp => /\{\{(.+?)\}\}/m, },
      :paragraph       => { :regexp => /([^\n]*)(\n|$)/m, },
    }

    def to_html
      @tokens = parse_style(BLOCK_STYLES, @text)

      @tokens = @tokens.inject([]) do |result, item|
        if !result.empty? && result.last[:type].eql?(item[:type]) &&
              (item[:type].eql?(:table) || item[:type].eql?(:blist) || item[:type].eql?(:nlist))
          result.last[:contents] << item[:contents]
        else
          result << item
        end
        result
      end

      compile_token(BLOCK_STYLE_RULES, @tokens)
    end


    def parse_style(styles, text)
      tokens = [
        {:type => :paragraph, :contents => text},
      ]

      styles.each do |style|
        part_tokens = []

        tokens.each do |token|
          t = token[:contents]

          while style[:regexp].match(t)
            part_tokens << {:type => :paragraph, :contents => $`}  unless $`.empty?
            part_tokens << {:type => style[:type], :contents => $&}

            t = $'
          end  if token[:type] == :paragraph

          part_tokens << {:type => token[:type], :contents => t}  unless t.empty?
        end

        tokens = part_tokens
      end

      tokens
    end

    def compile_token(style_rules, tokens)
      s = []

      tokens.each do |token|
        if style_rules.key?(token[:type])
          s << send("convert_#{token[:type]}", token[:contents])
        else
          s << token[:contents]
        end
      end

      s.join('')
    end

    def convert_pre(text)
      regexp = BLOCK_STYLE_RULES[:pre][:regexp]

      text.gsub(regexp) do
        t = $1

        t = CGI.escapeHTML(t)
        t = t.gsub(/\A(\s+)/m) { '&nbsp;' * $1.length }

        "<pre>#{t}</pre>\n"
      end
    end

    def convert_blockquote(text)
      regexp = BLOCK_STYLE_RULES[:blockquote][:regexp]

      text.gsub(regexp) do
        t = $1

        t = t.gsub(/(^\n)|(\n$)/m, '')
        t = CGI.escapeHTML(t)
        t = t.gsub(/^(\s+)/m) { '&nbsp;' * $1.length }

        "<blockquote>\n#{t}\n</blockquote>\n"
      end
    end

    def convert_table(text)
      regexp = BLOCK_STYLE_RULES[:table][:regexp]

      table = []

      table << "<table>"
      text.split(/(\n|$)/).each do |line|
        table << "<tr>"
        line.split(/\|\|/).each do |cell|
          unless cell.empty? || cell.chomp.empty?
            table << "<td>#{CGI.escapeHTML(cell)}</td>"
          end
        end
        table << "</tr>"
      end
      table << "</table>"

      table.join('') + "\n"
    end

    def convert_blist(text)
      regexp = BLOCK_STYLE_RULES[:blist][:regexp]

      blist = []

      blist << "<ul>"
      text.split(/(\n\$)/).each do |line|
        line.gsub(regexp) do
          num = $1.length + 1
          t = $2

          blist << "<li>#{CGI.escapeHTML(t)}</li>"
        end
      end
      blist << "</ul>"

      blist.join('') + "\n"
    end

    def convert_nlist(text)
      regexp = BLOCK_STYLE_RULES[:nlist][:regexp]

      nlist = []

      nlist << "<ol>"
      text.split(/(\n\$)/).each do |line|
        line.gsub(regexp) do
          num = $1.length + 1
          t = $2

          nlist << "<li>#{CGI.escapeHTML(t)}</li>"
        end
      end
      nlist << "</ol>"

      nlist.join('') + "\n"
    end

    def convert_title(text)
      regexp = BLOCK_STYLE_RULES[:title][:regexp]

      text.gsub(regexp) do
        num = $1.length + 1
        t = $2

        t = CGI.escapeHTML(t)

        "<h#{num}>#{t}</h#{num}>\n"
      end
    end

    def convert_paragraph(text)
      regexp = BLOCK_STYLE_RULES[:paragraph][:regexp]

      text.gsub(regexp) do |s|
        if s.empty?
          ""
        else
          "<p>#{convert_inline($1)}</p>\n"
        end
      end
    end

    INLINE_STYLES = [ 
      { :type => :link,   :regexp => /\[\[(.+?)\]\]/m },
      { :type => :plugin, :regexp => /\{\{(.+?)\}\}/m },
    ]

    INLINE_STYLE_RULES = {
      :link   => { :regexp => /\[\[(.+?)\]\]/m },
      :plugin => { :regexp => /\{\{(.+?)\}\}/m },
    }

    def convert_inline(text)
      tokens = parse_style(INLINE_STYLES, text)
      compile_token(INLINE_STYLE_RULES, tokens)
    end

    def convert_link(text)
      regexp = INLINE_STYLE_RULES[:link][:regexp]

      text.gsub(regexp) do
        t = $1
        if t =~ /(.+?)\|(.*)/
          "<a href=\"#{$2}\">#{$1}</a>"
        else
          "<a href=\"#{t}\">#{t}</a>"
        end
      end
    end

    def convert_plugin(text)
      return text if @plugin.nil?

      regexp = INLINE_STYLE_RULES[:plugin][:regexp]

      text.gsub(regexp) do |s|
        begin
          res = ''
          @plugin.instance_eval { res = eval($1) }
          res
        rescue Exception => e
          puts e.message
          s
        end
      end
    end
  end
end

if $0 == __FILE__

  require 'test/unit'

  class TC_CornoStyle < Test::Unit::TestCase
    def setup
    end

    def teardown
    end

    def test_pre_style
      contents = <<-RESULT_CONTENTS
<pre>class Foo
  def bar
  end
end
</pre>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, nil).to_html, contents)
>||
class Foo
  def bar
  end
end
||<
      CONTENS
    end

    def test_blockquote_style
      contents = <<-RESULT_CONTENTS
<blockquote>
abcdefghij
0123456789
</blockquote>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, nil).to_html, contents)
>>
abcdefghij
0123456789
<<
      CONTENS
    end

    def test_bulleted_list_style
      contents = <<-RESULT_CONTENTS
<ul><li>list1</li><li>list2</li><li>list3</li></ul>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, nil).to_html, contents)
- list1
- list2
- list3
      CONTENS
    end

    def test_numberd_list_style
      contents = <<-RESULT_CONTENTS
<ol><li>list1</li><li>list2</li><li>list3</li></ol>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, nil).to_html, contents)
+ list1
+ list2
+ list3
      CONTENS
    end

    def test_table_style
      contents = <<-RESULT_CONTENTS
<table><tr><td> a0 </td><td> b0 </td><td> c0 </td></tr><tr></tr><tr><td> a1 </td><td> b1 </td><td> c1 </td></tr><tr></tr></table>
<p></p>
<table><tr><td> A0 </td><td> b0 </td><td> c0 </td></tr><tr></tr><tr><td> A1 </td><td> B1 </td><td> C1 </td></tr><tr></tr></table>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, nil).to_html, contents)
|| a0 || b0 || c0 ||
|| a1 || b1 || c1 ||

|| A0 || b0 || c0 ||
|| A1 || B1 || C1 ||
      CONTENS
    end

    def test_title_style
      contents = <<-RESULT_CONTENTS
<h3>title1</h3>
<p></p>
<h4>title2</h4>
<p></p>
<h5>title3</h5>
<p></p>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, nil).to_html, contents)
** title1

*** title2

**** title3

      CONTENS
    end

    def test_paragraph_style
      contents = <<-RESULT_CONTENTS
<p>aiueo</p>
<p>kakikukeko</p>
<p>sasisuseso</p>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, nil).to_html, contents)
aiueo
kakikukeko
sasisuseso
      CONTENS
    end

    def test_paragraph_style_and_inline_link
      contents = <<-RESULT_CONTENTS
<p>aiueo</p>
<p>kakikukeko</p>
<p>sasisuseso</p>
<p></p>
<p><a href="http://localhost/doc/index.html">URL title</a></p>
<p><a href="http://localhost/cgi-bin/printenv.cgi">http://localhost/cgi-bin/printenv.cgi</a></p>
<p></p>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, nil).to_html, contents)
aiueo
kakikukeko
sasisuseso

[[URL title|http://localhost/doc/index.html]]
[[http://localhost/cgi-bin/printenv.cgi]]

      CONTENS
    end


    class Plugin
      def hello
        "Hello, Corno"
      end
    end

    def test_paragraph_style_and_inline_plugin
      contents = <<-RESULT_CONTENTS
<p>aiueo</p>
<p></p>
<p>Hello, Corno</p>
<p></p>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, Plugin.new).to_html, contents)
aiueo

{{hello}}

      CONTENS
    end

    def test_paragraph_style_and_inline_not_defined_plugin
      contents = <<-RESULT_CONTENTS
<p>aiueo</p>
<p></p>
<p>{{call_plugin}}</p>
<p></p>
      RESULT_CONTENTS

      assert_equal(CornoStyle::Default.new(<<-CONTENS, Plugin.new).to_html, contents)
aiueo

{{call_plugin}}

      CONTENS
    end
  end	
end

